

var attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;

// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
// but for Vue templates we can enforce a simple charset
var ncname = '[a-zA-Z_][\\w\\-\\.]*';
var qnameCapture = "((?:" + ncname + "\\:)?" + ncname + ")";
var startTagOpen = new RegExp(("^<" + qnameCapture));
var startTagClose = /^\s*(\/?)>/;
var endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>"));
var defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g;

var onRE = /^@|^v-on:/;
var dirRE = /^v-|^@|^:/;
var forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/;
var forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;
var stripParensRE = /^\(|\)$/g;

var argRE = /:(.*)$/;
var bindRE = /^:|^v-bind:/;
var modifierRE = /\.[^.]+/g;

var transforms;
var reCache = {};
// #7298: escape - to avoid being pased as HTML comment when inlined in page
var comment = /^<!\--/;
var conditionalComment = /^<!\[/;


var emptyObject = Object.freeze({});

function parse(template, options) {

    platformIsPreTag = options.isPreTag || no;
    platformMustUseProp = options.mustUseProp || no;
    platformGetTagNamespace = options.getTagNamespace || no;

    transforms = [transformNode,transformNode$1];
    preTransforms = [preTransformNode]

    var stack_parse = [];
    var root;
    var currentParent;

    // parse 内的变量，全部通过 传给 parseHTML 的 start,end,chars,comment 改变了，
    // 所以等待 parseHTML 执行完，parse 内的变量就全部都好了，比如 root
    parseHTML(template, {

        expectHTML: options.expectHTML,

        start: function start(tag, attrs, unary) {

            // 创建 AST 节点
            var element = createASTElement(tag, attrs, currentParent);

            // console.log("element",element)

            // return 
        
            // apply pre-transforms
            for (var i = 0; i < preTransforms.length; i++) {
                element = preTransforms[i](element, options) || element;
            }

            // console.log("start.element",tag)
        
            // 节点需要解析，并没有还没有处理
            if (!element.processed) {
        
                // structural directives
                processFor(element);
                processIf(element);
                processOnce(element);
        
                // element-scope stuff
                processElement(element, options);
            }
        
            // tree management
            if (!root) {
                root = element;
            }
        
            // stack_parse 长度为0
            else if (!stack_parse.length) {
        
                // allow root elements with v-if, v-else-if and v-else
                if (root.if && (element.elseif || element.else)) {
        
                    addIfCondition(root, {
                        exp: element.elseif,
                        block: element
                    });
                }
            }
        
            // 父节点就是上一个节点，直接放入 上一个节点的 children 数组中
            if (currentParent ) {
                
                // 说明前面节点有 v-if
                if (element.elseif || element.else) {
                    processIfConditions(element, currentParent);
                }
                else {
                    currentParent.children.push(element);
                    element.parent = currentParent;
                }
            }
        
            // 不是单标签，需要保存 stack_parse
            if (!unary) {
                currentParent = element;
                stack_parse.push(element);
        
            } else {
                closeElement(element);
            }
        },

        // 开始为 起始标签 开启闭合节点
        end:function end() {

            // remove trailing whitespace
            var element = stack_parse[stack_parse.length - 1];
            var lastNode = element.children[element.children.length - 1];

            // console.log("end",lastNode,lastNode.type === 3 , lastNode.text === ' ')
        
            // 去掉内容是一个空格的节点
            if (lastNode && lastNode.type === 3 && lastNode.text === ' ' ) {
                element.children.pop();
            }
        
            // pop stack_parse
            stack_parse.length -= 1;
            currentParent = stack_parse[stack_parse.length - 1];
            closeElement(element);
        },

        // 文字节点
        chars:function chars(text) {

            // console.log("currentParent",currentParent,"1"+text+"1",text.length)

            // 不存在根节点，需要根节点
            if (!currentParent) return
        
            var children = currentParent.children;

            // console.log("chat",children[children.length - 1])
        
            if (text) {
        
                var res;
        
                // 表达式的 text {{item}}，通过 parseText 解析成函数字符串表达式
                if ( text !== ' ' && (res = parseText(text))) {

                    // console.log("res",res,text)
        
                    children.push({
                        type: 2,
                        expression: res.expression,
                        tokens: res.tokens,
                        text: text
                    });
                }
        
                else if (
                    text !== ' ' || 
                    !children.length || 
                    children[children.length - 1].text !== ' '
                ) {
                    children.push({
                        type: 3,
                        text: text
                    });
                }
            }
        }
        
    });

    return root
}

function parseHTML(html, options) {

    // 保存所有标签的对象信息，tagName，attr，这样，在解析尾部标签的时候得到所属的层级关系以及父标签
    var stack = [];
    var expectHTML = options.expectHTML;
    var index = 0;
    var last, lastTag;

    while (html) {

        last = html;

        // console.log("lastTag",lastTag)

        // 1、lastTag 不存在，2、不在 script 或者 style 标签中
        if (
            !lastTag || 
            "script,style,textarea".indexOf(lastTag)==-1
        ) {

            var textEnd = html.indexOf('<');

            // 模板起始位置 是 标签
            if (textEnd === 0) {

                // End tag: new RegExp(("^<\\/" + qnameCapture + "[^>]*>"))
                // 匹配出 ["</a>", "a"]
                var endTagMatch = html.match(endTag);
                // console.log("endTagMatch",endTagMatch)

                if (endTagMatch) {

                    var curIndex = index;

                    // endTagMatch[0]="<a></a>"
                    advance(endTagMatch[0].length);

                    // endTagMatch[1]="a"，index 是 匹配完标签之后，curIndex 是当前开头
                    parseEndTag(endTagMatch[1], curIndex, index);

                    continue
                }

                // Start tag:
                var startTagMatch = parseStartTag();
                // console.log("startTagMatch",startTagMatch)

                if (startTagMatch) {
                    handleStartTag(startTagMatch);
                    continue
                }
            }

            var text = (void 0),
                rest = (void 0),
                next = (void 0);

            // 模板起始位置 不是 <，而是文字
            if (textEnd >= 0) {

                rest = html.slice(textEnd);

                while (
                    !endTag.test(rest) && 
                    !startTagOpen.test(rest) && 
                    !conditionalComment.test(rest)
                ) {

                    // < in plain text, be forgiving and treat it as text
                    next = rest.indexOf('<', 1);

                    if (next < 0) break

                    textEnd += next;
                    rest = html.slice(textEnd);
                }

                text = html.substring(0, textEnd);
                advance(textEnd);
            }

            // 模板中没有内容了,是空白字符
            if (textEnd < 0) {
                // console.log('textEnd--->',textEnd,!html,html.length);
                text = html;
                html = '';
            }

            if (options.chars && text) {
                // console.log('text--->',text);
                options.chars(text);
            }
        } 

        else {

            var endTagLength = 0;
            var stackedTag = lastTag.toLowerCase();
            var reStackedTag = reCache[stackedTag] || 
                    (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'));

            var rest$1 = html.replace(reStackedTag,
                (all, text, endTag) => {

                    endTagLength = endTag.length;

                    if ("script,style,textarea".indexOf(stackedTag)>-1 && stackedTag !== 'noscript') {
                        text = text.replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298
                            .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
                    }
                    if (options.chars) {
                        options.chars(text);
                    }
                    return ''
                }
            );

            // console.log("rest$1",rest$1)
// 
            // 改变 index
            index += html.length - rest$1.length;
            html = rest$1;
            parseEndTag(stackedTag, index - endTagLength, index);
        }

        // console.log("if (html === last) {",html==last,html,last)
        if (html === last) {
            options.chars && options.chars(html);
            break
        }
    }

    // Clean up any remaining tags
    parseEndTag();
    
    function parseStartTag() {

        // ["<div", "div", index: 0]
        var start = html.match(startTagOpen);


        if (start) {

            var match = {
                tagName: start[1],
                attrs: [],
                start: index
            };

            // start[0] 是 <div
            advance(start[0].length);

            var end, attr;

            // 匹配 属性 内容，保存属性列表
            while (
                // 匹配不到闭合标签，开始匹配 属性内容
                ! (end = html.match(startTagClose))
                &&
                (attr = html.match(attribute))
            ) {
                advance(attr[0].length);
                match.attrs.push(attr);
            }
            // console.log("end",end)

            if (end) {

                // 如果是单标签 <input />，那么 unarySlash 的值是 /
                match.unarySlash = end[1];
                advance(end[0].length);
                match.end = index;
                return match
            }
        }
    }

    function handleStartTag(match) {

        var tagName = match.tagName;
        var unarySlash = match.unarySlash;

        if (expectHTML) {
            // && tag 不是语句型元素
            if (lastTag === 'p' ) {
                parseEndTag(lastTag);
            }
            // canBeLeftOpenTag$$1(tagName) && 
            if (lastTag === tagName) {
                parseEndTag(tagName);
            }
        }

        // 看是不是单标签，input，img 这些, unary 【单项】
        var unary = isUnaryTag(tagName) || !!unarySlash;

        var l = match.attrs.length;
        var attrs = new Array(l);

        for (var i = 0; i < l; i++) {

            var args = match.attrs[i];
            
            // console.log("value",args)

            // 属性值
            // args = [" name=111", "name", "=", undefined, undefined, "111" ]
            var value = args[3] || args[4] || args[5] || '';

            attrs[i] = {
                name: args[1],
                value: value
            };
        }

        if (!unary) {
            stack.push({
                tag: tagName,
                lowerCasedTag: tagName.toLowerCase(),
                attrs: attrs
            });
            lastTag = tagName;
        }

        if (options.start) {
            options.start(tagName, attrs, unary, match.start, match.end);
        }
    }

    function parseEndTag(tagName, start, end) {

        var pos, lowerCasedTagName;

        if (start == null) {
            start = index;
        }

        if (end == null) {
            end = index;
        }

        // 标签名变成小写
        if (tagName) {
            lowerCasedTagName = tagName.toLowerCase();
        }

        // Find the closest opened tag of the same type
        // 从最后面开始查找，闭合标签
        // 肯定是从 最近的一个 （子元素开始标签） 开始匹配起来
        if (tagName) {

            // 从结尾开始找，如果在 stack 中找不到，pos 最后是 -1
            for (pos = stack.length - 1; pos >= 0; pos--) {
                if (stack[pos].lowerCasedTag === lowerCasedTagName) {
                    break
                }
            }
        }
        else {

            // If no tag name is provided, clean shop
            pos = 0
        }

        // 匹配的 闭合 标签，在 stack 中找得到 这个 元素
        if (pos >= 0) {

            // 批量闭合标签，为了避免有些刁民忘记写闭合标签
            // 比如 stack.len = 7 , pos=5 , i>=5，要闭合该标签后面所有的标签
            for (var i = stack.length - 1; i >= pos; i--) {

                // 找不到匹配的 闭合标签，
                // i 到了哪个位置才等于pos，说明有 stack.len - pos 个元素没有闭合标签
                // 我要一个一个提示，但是还是会一个个手动闭合

                // 找不到闭合的标签，好吧，我给你手动闭合，默认给你填上 闭合标签
                // 直接使用 stack 中的 标签名
                // 应该是 不管是否有闭合标签，都会 自动给你闭合，但是如果没有闭合标签，会警告一下你
                if (options.end) {
                    options.end(stack[i].tag, start, end);
                }
            }

            // 匹配完闭合标签之后，就把 匹配了的标签头 给 移除了
            stack.length = pos;
            lastTag = pos && stack[pos - 1].tag;
        }

        // 碰到 没有匹配的 p 尾标签，默默给他一个头标签
        // br 虽然没有存在 stack 头标签中，但是还是仍然会保留
        else if (lowerCasedTagName === 'br') {
            if (options.start) {
                options.start(tagName, [], true, start, end);
            }
        }

        // 匹配到 </p>, 这个</p> 先前并没有存放 stack 中，但是仍然保留，并且自动闭合标签给他
        else if (lowerCasedTagName === 'p') {

            if (options.start) {
                options.start(tagName, [], false, start, end);
            }

            if (options.end) {
                options.end(tagName, start, end);
            }
        }
    }

    function advance(n) {
        index += n;
        html = html.substring(n);
    }
}

// 匹配文字是否有表达式
function parseText(
    text
) {
    var tagRE =  defaultTagRE;

    if(!tagRE.test(text)) return

    var tokens = [];
    var rawTokens = [];
    var lastIndex = tagRE.lastIndex = 0;
    var match, index, tokenValue;

    // /\{\{((?:.|\n)+?)\}\}/
    while((match = tagRE.exec(text))) {
        index = match.index;

        // push text token
        if(index > lastIndex) {
            rawTokens.push(tokenValue = text.slice(lastIndex, index));
            tokens.push(JSON.stringify(tokenValue));
        }

        // tag token
        var exp = parseFilters(match[1].trim());
        tokens.push(("_s(" + exp + ")"));
        rawTokens.push({
            '@binding': exp
        });
        lastIndex = index + match[0].length;
    }
    if(lastIndex < text.length) {
        rawTokens.push(
            tokenValue = text.slice(lastIndex)
        );
        tokens.push(JSON.stringify(tokenValue));
    }
    return {
        // 比如 tokens = ['hello,', _s(name), '.']
        // expression = 'hello' + _s(name) + '.';
        expression: tokens.join('+'),
        tokens: rawTokens
    }
}


var parseStyleText = function(cssText) {

    var res = {};
    var listDelimiter = /;(?![^(]*\))/g;
    var propertyDelimiter = /:(.+)/;

    cssText.split(listDelimiter).forEach(function(item) {
        if (item) {
            var tmp = item.split(propertyDelimiter);
            tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim());
        }
    });
    return res
}

function createASTElement(tag, attrs, parent) {
    return {
        type: 1,
        tag: tag,
        attrsList: attrs,

        // g：复制一份 attrs
        attrsMap: makeAttrsMap(attrs),
        parent: parent,
        children: []
    }
}

function closeElement(element) {
    
}

function transformNode(el, options) {

    var staticClass = getAndRemoveAttr(el, 'class');

    if (staticClass) {
        el.staticClass = JSON.stringify(staticClass);
    }

    var classBinding = getBindingAttr(el, 'class', false /* getStatic */ );


    if (classBinding) {
        el.classBinding = classBinding;
    }
}

function transformNode$1(el, options) {

    var staticStyle = getAndRemoveAttr(el, 'style');

    if (staticStyle) {
        el.staticStyle = JSON.stringify(parseStyleText(staticStyle));
    }

    var styleBinding = getBindingAttr(el, 'style', false /* getStatic */ );

    
    if (styleBinding) {
        el.styleBinding = styleBinding;
    }
}

function preTransformNode(el, options) {

    if (el.tag === 'input') {

        var map = el.attrsMap;

        if (!map['v-model']) {
            return
        }

        var typeBinding;

        if (map[':type'] || map['v-bind:type']) {
            typeBinding = getBindingAttr(el, 'type');
        }
        if (!map.type && !typeBinding && map['v-bind']) {
            typeBinding = "(" + (map['v-bind']) + ").type";
        }

        if (typeBinding) {

            var ifCondition = getAndRemoveAttr(el, 'v-if', true);
            var ifConditionExtra = ifCondition ? ("&&(" + ifCondition + ")") : "";
            var hasElse = getAndRemoveAttr(el, 'v-else', true) != null;
            var elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true);

            // 1. checkbox
            var branch0 = cloneASTElement(el);

            // process for on the main node
            processFor(branch0);
            addRawAttr(branch0, 'type', 'checkbox');
            processElement(branch0, options);

            branch0.processed = true; // prevent it from double-processed
            branch0.if = "(" + typeBinding + ")==='checkbox'" + ifConditionExtra;
            addIfCondition(branch0, {
                exp: branch0.if,
                block: branch0
            });


            // 2. add radio else-if condition
            var branch1 = cloneASTElement(el);
            getAndRemoveAttr(branch1, 'v-for', true);
            addRawAttr(branch1, 'type', 'radio');
            processElement(branch1, options);

            addIfCondition(branch0, {
                exp: "(" + typeBinding + ")==='radio'" + ifConditionExtra,
                block: branch1
            });


            // 3. other
            var branch2 = cloneASTElement(el);
            getAndRemoveAttr(branch2, 'v-for', true);
            addRawAttr(branch2, ':type', typeBinding);
            processElement(branch2, options);
            addIfCondition(branch0, {
                exp: ifCondition,
                block: branch2
            });

            if (hasElse) {
                branch0.else = true;

            } else if (elseIfCondition) {
                branch0.elseif = elseIfCondition;
            }

            return branch0
        }
    }
}
